Popular Searches
Popular Course Categories
Popular Courses

Responsive Flutter Best Practices

Responsive Flutter Best Practices

Flutter Responsive Design


Responsive Flutter Best Practices


Responsive design in Flutter means creating interfaces that automatically adjust to the available screen space, window size, orientation, and input method. A responsive Flutter application should provide a comfortable experience on phones, tablets, foldables, desktops, and web browsers.


Flutter distinguishes between responsive design, which focuses on fitting UI into available space, and adaptive design, which focuses on making the UI usable in that space. In practice, modern Flutter applications often need both. :contentReference[oaicite:0]{index=0}




1. Why Responsive Design Is Important in Flutter


A Flutter application can run on many screen sizes and platforms. A layout that looks good on a mobile phone may become too stretched, crowded, or difficult to use on a tablet or desktop.



  • Provides a better user experience across devices.

  • Prevents overflow and clipped content.

  • Improves usability on tablets and desktop screens.

  • Supports resizable application windows.

  • Works better with foldables and multi-window environments.

  • Supports different input methods such as touch, mouse, trackpad, and keyboard.

  • Makes applications easier to maintain because the same codebase can support multiple layouts.




2. Responsive vs Adaptive Design








Responsive DesignAdaptive Design
Fits UI into available space.Chooses an appropriate UI structure for the available space.
Changes sizes, spacing, columns, and positions.May change navigation or information architecture.
Example: changing a 4-column grid to 2 columns.Example: changing bottom navigation to NavigationRail.
Focuses mainly on layout.Focuses on usability and interaction.

Flutter recommends considering both responsive and adaptive behavior when building applications for multiple form factors. :contentReference[oaicite:1]{index=1}




3. Best Practice: Design for Available Space


Do not assume that a particular device type always has a particular amount of space. A tablet can run an application in split-screen mode, a desktop application can be resized, and a phone may display an application in a constrained window.


Therefore, layout decisions should generally be based on the available window size rather than assumptions such as "phone", "tablet", or "desktop". :contentReference[oaicite:2]{index=2}


Example


LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth < 600) {
      return const MobileLayout();
    }
    return const LargeScreenLayout();
  },
)



4. Use MediaQuery for Application Window Information


MediaQuery provides information about the current application window, including its size and accessibility-related settings.


Example


final size = MediaQuery.sizeOf(context);

print('Width: ${size.width}');
print('Height: ${size.height}');


Responsive Width Example


Widget build(BuildContext context) {
  final width = MediaQuery.sizeOf(context).width;

  return Scaffold(
    body: Center(
      child: Text(
        width < 600
            ? 'Small Screen'
            : 'Large Screen',
      ),
    ),
  );
}


For modern responsive layouts, Flutter documentation recommends using the available window size rather than relying on physical device assumptions. :contentReference[oaicite:3]{index=3}




5. Use LayoutBuilder for Local Responsive Layouts


LayoutBuilder is useful when a widget needs to respond to the space provided by its parent rather than the entire application window.


LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth < 400) {
      return const Text('Compact Layout');
    }

    return const Text('Wide Layout');
  },
)


MediaQuery vs LayoutBuilder








MediaQueryLayoutBuilder
Works with application window information.Works with parent constraints.
Useful for page-level decisions.Useful for component-level decisions.
Returns size information.Provides BoxConstraints.
Good for navigation/layout changes.Good for reusable responsive widgets.

Flutter's adaptive design guidance recommends MediaQuery.sizeOf for window-level measurements and LayoutBuilder when the layout depends on the constraints available to a specific widget. :contentReference[oaicite:4]{index=4}




6. Use Meaningful Breakpoints


A breakpoint is a width at which the application changes its layout.


const double mobileBreakpoint = 600;
const double tabletBreakpoint = 900;
const double desktopBreakpoint = 1200;

These values are examples, not universal device definitions. Choose breakpoints according to where your actual layout needs to change. Flutter documentation emphasizes using available window size rather than device type. :contentReference[oaicite:5]{index=5}


Three-Level Layout


Widget buildResponsiveLayout(BuildContext context) {
  final width = MediaQuery.sizeOf(context).width;

  if (width < 600) {
    return const MobileLayout();
  } else if (width < 1200) {
    return const TabletLayout();
  } else {
    return const DesktopLayout();
  }
}




7. Do Not Check Whether the Device Is a Phone or Tablet


A common mistake is writing layout logic based on device categories.


Bad Approach


if (isTablet) {
  return TabletLayout();
} else {
  return MobileLayout();
}

Better Approach


final width = MediaQuery.sizeOf(context).width;

if (width >= 600) {
  return const LargeLayout();
}

return const SmallLayout();


The available application window can differ from the physical screen because of resizing, split-screen mode, desktop windows, ChromeOS, and other environments. :contentReference[oaicite:6]{index=6}




8. Avoid Orientation-Only Layout Decisions


Do not assume that portrait always means mobile and landscape always means tablet or desktop. A wide desktop window and a landscape phone may have very different available widths.


Less Reliable Approach


if (MediaQuery.orientationOf(context) == Orientation.landscape) {
  return const DesktopLayout();
}

Better Approach


final width = MediaQuery.sizeOf(context).width;

if (width >= 900) {
  return const WideLayout();
}

return const CompactLayout();


Flutter recommends using available size with MediaQuery.sizeOf or LayoutBuilder instead of using orientation alone for major layout decisions. :contentReference[oaicite:7]{index=7}




9. Avoid Fixed Widths


Hard-coded widths can easily create overflow on smaller screens.


Problematic Example


Container(
  width: 700,
  child: const Text('Responsive content'),
)

Better Example


Container(
  width: double.infinity,
  padding: const EdgeInsets.all(16),
  child: const Text('Responsive content'),
)

For large screens, however, using every available pixel can make text fields and content unnecessarily wide. A maximum width is often better.




10. Use ConstrainedBox for Large Screens


Center(
  child: ConstrainedBox(
    constraints: const BoxConstraints(
      maxWidth: 900,
    ),
    child: const Padding(
      padding: EdgeInsets.all(24),
      child: Text(
        'This content remains readable on large screens.',
      ),
    ),
  ),
)

This technique prevents content from becoming excessively wide on desktop or web screens. Flutter's large-screen guidance specifically recommends avoiding layouts where text or boxes simply stretch across the entire available width. :contentReference[oaicite:8]{index=8}




11. Use SafeArea


SafeArea helps prevent content from being hidden behind system UI, camera cutouts, notches, rounded display edges, and status bars.


Scaffold(
  body: SafeArea(
    child: Column(
      children: [
        const Text('Dashboard'),
        Expanded(
          child: ListView(
            children: const [
              ListTile(title: Text('Item 1')),
              ListTile(title: Text('Item 2')),
            ],
          ),
        ),
      ],
    ),
  ),
)

Wrapping the main body of a Scaffold with SafeArea is a useful starting point for many applications, although the correct placement depends on the design. :contentReference[oaicite:9]{index=9}




12. Build Mobile-First Interfaces


Start with a simple layout that works well in limited space, then enhance it for larger windows.


Mobile


Column(
  children: [
    const ProductImage(),
    const ProductDetails(),
    const BuyButton(),
  ],
)

Large Screen


Row(
  children: [
    Expanded(
      child: ProductImage(),
    ),
    Expanded(
      child: Column(
        children: const [
          ProductDetails(),
          BuyButton(),
        ],
      ),
    ),
  ],
)

This approach helps ensure that the most important functionality remains available even when screen space is limited.




13. Use Row and Column Responsively


One common responsive pattern is switching between vertical and horizontal layouts.


LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth < 600) {
      return const Column(
        children: [
          ProfileImage(),
          ProfileDetails(),
        ],
      );
    }

    return const Row(
      children: [
        Expanded(child: ProfileImage()),
        Expanded(child: ProfileDetails()),
      ],
    );
  },
)




14. Use Expanded and Flexible


Expanded and Flexible help widgets share available space without relying on fixed widths.


Row(
  children: [
    Expanded(
      flex: 2,
      child: Container(
        height: 100,
        child: const Center(child: Text('Main')),
      ),
    ),
    Expanded(
      flex: 1,
      child: Container(
        height: 100,
        child: const Center(child: Text('Side')),
      ),
    ),
  ],
)

The flex values determine how the available space is distributed.




15. Make Grids Responsive


A grid should generally change based on available width instead of using a hard-coded number of columns for a specific device.


GridView.builder(
  gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
    maxCrossAxisExtent: 280,
    mainAxisSpacing: 16,
    crossAxisSpacing: 16,
    childAspectRatio: 1.2,
  ),
  itemCount: products.length,
  itemBuilder: (context, index) {
    return ProductCard(product: products[index]);
  },
)

A maximum item width allows the number of columns to adjust naturally as the available width changes. Flutter's large-screen guidance recommends this type of approach for responsive grids. :contentReference[oaicite:10]{index=10}




16. Responsive Navigation


Navigation is one of the most important parts of adaptive design. A compact window may work better with bottom navigation, while a wider window can use a navigation rail.


Widget buildNavigation(BuildContext context) {
  final width = MediaQuery.sizeOf(context).width;

  if (width < 600) {
    return const NavigationBar(
      destinations: [
        NavigationDestination(
          icon: Icon(Icons.home),
          label: 'Home',
        ),
        NavigationDestination(
          icon: Icon(Icons.person),
          label: 'Profile',
        ),
      ],
    );
  }

  return const NavigationRail(
    destinations: [
      NavigationRailDestination(
        icon: Icon(Icons.home),
        label: Text('Home'),
      ),
      NavigationRailDestination(
        icon: Icon(Icons.person),
        label: Text('Profile'),
      ),
    ],
    selectedIndex: 0,
  );
}


The exact breakpoint should be based on the space required by your design. Flutter's adaptive documentation gives 600 logical pixels as an example for switching between some compact and larger navigation patterns. :contentReference[oaicite:11]{index=11}




17. Responsive Sidebar Layout


Large screens can show navigation and content side by side.


LayoutBuilder(
  builder: (context, constraints) {
    if (constraints.maxWidth < 700) {
      return const MobileHomePage();
    }

    return Row(
      children: [
        SizedBox(
          width: 260,
          child: Sidebar(),
        ),
        const VerticalDivider(width: 1),
        const Expanded(
          child: DetailsPage(),
        ),
      ],
    );
  },
)


This sidebar-and-details pattern is commonly useful for tablet and desktop layouts. :contentReference[oaicite:12]{index=12}




18. Use Wrap for Responsive Buttons and Chips


Wrap is useful when items should move to another line when horizontal space becomes insufficient.


Wrap(
  spacing: 8,
  runSpacing: 8,
  children: [
    ElevatedButton(
      onPressed: () {},
      child: const Text('All'),
    ),
    ElevatedButton(
      onPressed: () {},
      child: const Text('Popular'),
    ),
    ElevatedButton(
      onPressed: () {},
      child: const Text('New'),
    ),
    ElevatedButton(
      onPressed: () {},
      child: const Text('Discount'),
    ),
  ],
)



19. Responsive Forms


Forms often become too wide on desktop screens. Use a maximum width.


Center(
  child: ConstrainedBox(
    constraints: const BoxConstraints(
      maxWidth: 500,
    ),
    child: Padding(
      padding: const EdgeInsets.all(24),
      child: Column(
        children: [
          TextField(
            decoration: const InputDecoration(
              labelText: 'Name',
            ),
          ),
          const SizedBox(height: 16),
          TextField(
            decoration: const InputDecoration(
              labelText: 'Email',
            ),
          ),
          const SizedBox(height: 16),
          SizedBox(
            width: double.infinity,
            child: ElevatedButton(
              onPressed: () {},
              child: const Text('Submit'),
            ),
          ),
        ],
      ),
    ),
  ),
)



20. Responsive Login Screen


LayoutBuilder(
  builder: (context, constraints) {
    final isWide = constraints.maxWidth >= 800;

    return Center(
      child: ConstrainedBox(
        constraints: BoxConstraints(
          maxWidth: isWide ? 900 : 500,
        ),
        child: Padding(
          padding: const EdgeInsets.all(24),
          child: isWide
              ? Row(
                  children: const [
                    Expanded(child: LoginIllustration()),
                    Expanded(child: LoginForm()),
                  ],
                )
              : const LoginForm(),
        ),
      ),
    );
  },
)




21. Responsive Text


Avoid unnecessarily large fixed text sizes on small screens. Prefer theme-based typography and allow text to wrap naturally.


Text(
  'Welcome to Flutter',
  style: Theme.of(context).textTheme.headlineMedium,
  textAlign: TextAlign.center,
)

When users increase system text size for accessibility, the UI should remain usable rather than forcing a fixed text size.




22. Responsive Spacing


Spacing can also respond to available width.


final width = MediaQuery.sizeOf(context).width;

final padding = width < 600
    ? 16.0
    : 32.0;

return Padding(
  padding: EdgeInsets.all(padding),
  child: const Text('Responsive content'),
);


Do not create dozens of arbitrary spacing breakpoints. Keep a small, consistent spacing system.




23. Break Large Widgets into Smaller Widgets


Large widgets become difficult to maintain when responsive logic is added. Break complex screens into smaller reusable widgets.


class DashboardPage extends StatelessWidget {
  const DashboardPage({super.key});

  @override
  Widget build(BuildContext context) {
    return const Column(
      children: [
        DashboardHeader(),
        DashboardStats(),
        DashboardChart(),
        DashboardRecentOrders(),
      ],
    );
  }
}


Flutter's responsive best-practice guidance recommends breaking complex widgets into smaller widgets. This improves maintainability and can also reduce unnecessary rebuild work, especially when widgets can be made const. :contentReference[oaicite:13]{index=13}




24. Prefer const Widgets


Use const wherever a widget and its inputs are compile-time constants.


const Padding(
  padding: EdgeInsets.all(16),
  child: Text('Welcome'),
)

Using smaller const widgets can help Flutter reuse widget instances and keep widget rebuilds more efficient. :contentReference[oaicite:14]{index=14}




25. Support Touch, Mouse, Trackpad, and Keyboard


A responsive application should not only change its visual layout. It should also provide suitable interaction for different input methods.



  • Touch users need comfortable tap targets.

  • Mouse users may expect hover feedback.

  • Trackpad users need smooth scrolling.

  • Keyboard users need logical focus traversal.

  • Desktop users may benefit from keyboard shortcuts.


Flutter's Material widgets provide many standard interaction behaviors, while custom widgets may need additional handling. :contentReference[oaicite:15]{index=15}


MouseRegion Example


MouseRegion(
  onEnter: (_) {
    setState(() {
      isHovered = true;
    });
  },
  onExit: (_) {
    setState(() {
      isHovered = false;
    });
  },
  child: Container(
    padding: const EdgeInsets.all(20),
    child: const Text('Hover me'),
  ),
)



26. Support Keyboard Navigation


Users on desktop and users who depend on assistive technologies may rely on keyboard navigation.


Focus(
  autofocus: true,
  child: TextField(
    decoration: const InputDecoration(
      labelText: 'Search',
    ),
  ),
)

Built-in Material controls generally provide focus and keyboard traversal behavior, but custom widgets should be tested carefully. :contentReference[oaicite:16]{index=16}




27. Preserve Application State


Changing screen size, orientation, window size, or device configuration should not unnecessarily destroy user progress.


Examples of state that should be preserved include:



  • Form input.

  • Selected navigation item.

  • Scroll position.

  • Expanded/collapsed sections.

  • Selected filters.

  • Current page.


PageStorageKey Example


ListView.builder(
  key: const PageStorageKey('products-list'),
  itemCount: products.length,
  itemBuilder: (context, index) {
    return ListTile(
      title: Text(products[index]),
    );
  },
)

Flutter recommends preserving application state when the window changes size, rotates, or folds/unfolds. PageStorageKey can help preserve scroll state in appropriate list scenarios. :contentReference[oaicite:17]{index=17}




28. Do Not Lock Orientation Without a Strong Reason


Responsive applications should generally be able to handle different window sizes and orientations. Locking an application to one orientation can make future adaptive behavior more difficult and may reduce accessibility or multi-window flexibility. :contentReference[oaicite:18]{index=18}


Instead of designing only for portrait mode, test:



  • Portrait phone.

  • Landscape phone.

  • Portrait tablet.

  • Landscape tablet.

  • Desktop window.

  • Resized desktop window.




29. Avoid Horizontal Overflow


Horizontal overflow is one of the most common problems in responsive Flutter interfaces.


Problem


Row(
  children: [
    Container(width: 500, child: const Text('A')),
    Container(width: 500, child: const Text('B')),
  ],
)

The combined width may exceed the available space.


Possible Solution


Row(
  children: [
    Expanded(
      child: Container(
        child: const Text('A'),
      ),
    ),
    Expanded(
      child: Container(
        child: const Text('B'),
      ),
    ),
  ],
)

Another option is to use Wrap, a responsive grid, or a horizontal scroll view when horizontal scrolling is actually appropriate.




30. Use Scrollable Layouts Carefully


Small screens have limited vertical space. Long content should generally be placed inside an appropriate scrollable widget.


Scaffold(
  body: SafeArea(
    child: SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        children: [
          const Text('Long Content'),
          const SizedBox(height: 800),
          ElevatedButton(
            onPressed: () {},
            child: const Text('Continue'),
          ),
        ],
      ),
    ),
  ),
)

For large lists, prefer lazy widgets such as ListView.builder rather than building every item at once.




31. Responsive Dashboard Example


class DashboardPage extends StatelessWidget {
  const DashboardPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Dashboard'),
      ),
      body: SafeArea(
        child: LayoutBuilder(
          builder: (context, constraints) {
            final width = constraints.maxWidth;

            return SingleChildScrollView(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text(
                    'Overview',
                    style: Theme.of(context)
                        .textTheme
                        .headlineSmall,
                  ),
                  const SizedBox(height: 20),
                  GridView.count(
                    crossAxisCount: width < 600
                        ? 1
                        : width < 1000
                            ? 2
                            : 4,
                    crossAxisSpacing: 16,
                    mainAxisSpacing: 16,
                    shrinkWrap: true,
                    physics: const NeverScrollableScrollPhysics(),
                    children: const [
                      StatCard(
                        title: 'Users',
                        value: '12,450',
                      ),
                      StatCard(
                        title: 'Orders',
                        value: '3,240',
                      ),
                      StatCard(
                        title: 'Revenue',
                        value: '₹8,45,000',
                      ),
                      StatCard(
                        title: 'Pending',
                        value: '128',
                      ),
                    ],
                  ),
                ],
              ),
            );
          },
        ),
      ),
    );
  }
}

class StatCard extends StatelessWidget {
  final String title;
  final String value;

  const StatCard({
    super.key,
    required this.title,
    required this.value,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(title),
            const SizedBox(height: 8),
            Text(
              value,
              style: Theme.of(context)
                  .textTheme
                  .headlineSmall,
            ),
          ],
        ),
      ),
    );
  }
}




32. Create a Reusable Responsive Layout Widget


When the same breakpoint logic is needed throughout an application, create a reusable widget.


class ResponsiveLayout extends StatelessWidget {
  final Widget mobile;
  final Widget tablet;
  final Widget desktop;

  const ResponsiveLayout({
    super.key,
    required this.mobile,
    required this.tablet,
    required this.desktop,
  });

  @override
  Widget build(BuildContext context) {
    final width = MediaQuery.sizeOf(context).width;

    if (width < 600) {
      return mobile;
    }

    if (width < 1200) {
      return tablet;
    }

    return desktop;
  }
}


Usage


ResponsiveLayout(
  mobile: const MobileDashboard(),
  tablet: const TabletDashboard(),
  desktop: const DesktopDashboard(),
)



33. Separate UI from Business Logic


Responsive behavior should not duplicate business logic.


For example, a shopping application may have three different layouts but the same product data:


class Product {
  final String name;
  final double price;

  const Product({
    required this.name,
    required this.price,
  });
}


Then the same product data can be passed to mobile, tablet, and desktop widgets.


ResponsiveLayout(
  mobile: ProductList(products: products),
  tablet: ProductGrid(products: products),
  desktop: ProductGridWide(products: products),
)

This keeps the responsive layer focused on presentation while the data and business logic remain reusable.




34. Design to the Strengths of Each Form Factor


Responsive design does not mean that every device must have exactly the same interface.



  • Mobile: prioritize quick actions, simplified navigation, and touch interaction.

  • Tablet: take advantage of additional space for multi-column layouts.

  • Desktop: support denser information displays, keyboard interaction, hover behavior, and side navigation where appropriate.

  • Web: consider browser navigation, deep links, URL-based navigation, and flexible window sizes.


Flutter recommends considering the strengths and capabilities of each form factor rather than simply duplicating the exact same interface everywhere. :contentReference[oaicite:19]{index=19}




35. Consider Large Screens Carefully


Large screens include tablets, foldables, ChromeOS devices, iPads, desktop applications, and web applications. A large display provides more space, but simply stretching the mobile UI can create poor usability. :contentReference[oaicite:20]{index=20}


Useful large-screen techniques include:



  • Maximum content widths.

  • Multi-column layouts.

  • Navigation rails or sidebars.

  • List-detail layouts.

  • Responsive grids.

  • More information displayed simultaneously.

  • Keyboard and mouse support.




36. List-Detail Responsive Pattern


A common application pattern is showing a list and selected details.


Small Screen


ListPage → DetailPage

Large Screen


Row(
  children: [
    SizedBox(
      width: 300,
      child: ListPage(),
    ),
    const VerticalDivider(width: 1),
    Expanded(
      child: DetailPage(),
    ),
  ],
)

This pattern is useful for email applications, contact applications, messaging applications, product catalogs, and admin panels.




37. Responsive Dialogs


Dialogs should not become excessively wide on desktop screens.


Dialog(
  child: ConstrainedBox(
    constraints: const BoxConstraints(
      maxWidth: 500,
    ),
    child: Padding(
      padding: const EdgeInsets.all(24),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          const Text('Edit Profile'),
          const SizedBox(height: 16),
          const TextField(),
          const SizedBox(height: 16),
          ElevatedButton(
            onPressed: () {},
            child: const Text('Save'),
          ),
        ],
      ),
    ),
  ),
)



38. Responsive Images


Images should adapt to their available space and preserve their aspect ratio.


AspectRatio(
  aspectRatio: 16 / 9,
  child: Image.network(
    imageUrl,
    fit: BoxFit.cover,
  ),
)

For large layouts, the image can occupy one section of a row, while on small screens it can move above the text.




39. Use Theme Instead of Hard-Coding Styles


A responsive application is easier to maintain when typography, colors, shapes, and spacing are centralized.


MaterialApp(
  theme: ThemeData(
    colorSchemeSeed: Colors.blue,
    useMaterial3: true,
  ),
  home: const HomePage(),
)

Then use the theme throughout the application:


Text(
  'Dashboard',
  style: Theme.of(context).textTheme.headlineMedium,
)



40. Test Different Window Sizes


Responsive design should be tested at more than one screen size.



  • Small phone width.

  • Large phone width.

  • Tablet width.

  • Desktop width.

  • Very wide desktop window.

  • Very narrow resized window.

  • Portrait orientation.

  • Landscape orientation.

  • Split-screen or constrained windows.

  • Different text scaling settings.

  • Touch input.

  • Mouse input.

  • Keyboard navigation.




41. Common Responsive Flutter Mistakes


Mistake 1: Using Too Many Fixed Widths


Fixed widths can cause overflow when the available space changes.


Mistake 2: Checking Device Type


Do not make layout decisions based on whether the device is a phone or tablet. Use available window size instead. :contentReference[oaicite:21]{index=21}


Mistake 3: Using Orientation as the Main Breakpoint


Orientation alone does not tell you how much space the application actually has.


Mistake 4: Stretching Everything on Desktop


Large screens should not automatically result in extremely wide text fields or paragraphs.


Mistake 5: Ignoring Keyboard and Mouse


Desktop and web applications should support appropriate input methods.


Mistake 6: Losing State During Resize


Responsive changes should not unnecessarily reset forms, selections, or scroll positions.


Mistake 7: Creating One Huge Widget


Break complex interfaces into smaller widgets to make responsive behavior easier to manage.




42. Responsive Flutter Best Practices Checklist



  • Use available window size to make layout decisions.

  • Use MediaQuery.sizeOf for window-level information.

  • Use LayoutBuilder for local parent constraints.

  • Choose breakpoints based on layout requirements.

  • Avoid device-type checks for layout decisions.

  • Avoid orientation-only layout logic.

  • Avoid unnecessary fixed widths.

  • Use Expanded and Flexible where appropriate.

  • Use Wrap for items that can move between lines.

  • Use responsive grids.

  • Limit maximum content width on large screens.

  • Use SafeArea where appropriate.

  • Support touch, mouse, trackpad, and keyboard input.

  • Preserve important application state.

  • Break large widgets into smaller reusable widgets.

  • Use const widgets where possible.

  • Test multiple screen sizes and window configurations.

  • Design for the strengths of different form factors.




43. Practical Responsive App Workflow



  1. Identify the important content and user actions.

  2. Build a simple mobile-friendly layout.

  3. Identify where the mobile layout becomes crowded.

  4. Create meaningful breakpoints at those points.

  5. Use LayoutBuilder or MediaQuery.sizeOf.

  6. Adapt navigation when necessary.

  7. Adapt grids and columns.

  8. Limit content width on large screens.

  9. Add mouse and keyboard interactions.

  10. Preserve application state.

  11. Test resizing, orientation changes, and multiple form factors.

  12. Refactor repeated responsive logic into reusable widgets.




44. Complete Responsive Example


import 'package:flutter/material.dart';

void main() {
  runApp(const ResponsiveApp());
}

class ResponsiveApp extends StatelessWidget {
  const ResponsiveApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorSchemeSeed: Colors.blue,
        useMaterial3: true,
      ),
      home: const ResponsiveHomePage(),
    );
  }
}

class ResponsiveHomePage extends StatelessWidget {
  const ResponsiveHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Responsive Flutter'),
      ),
      body: SafeArea(
        child: LayoutBuilder(
          builder: (context, constraints) {
            final width = constraints.maxWidth;

            if (width < 600) {
              return const MobileContent();
            }

            if (width < 1000) {
              return const TabletContent();
            }

            return const DesktopContent();
          },
        ),
      ),
    );
  }
}

class MobileContent extends StatelessWidget {
  const MobileContent({super.key});

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.all(16),
      children: const [
        Text(
          'Mobile Layout',
          style: TextStyle(fontSize: 24),
        ),
        SizedBox(height: 16),
        Card(child: ListTile(title: Text('Dashboard'))),
        Card(child: ListTile(title: Text('Products'))),
        Card(child: ListTile(title: Text('Orders'))),
      ],
    );
  }
}

class TabletContent extends StatelessWidget {
  const TabletContent({super.key});

  @override
  Widget build(BuildContext context) {
    return GridView.count(
      padding: const EdgeInsets.all(24),
      crossAxisCount: 2,
      crossAxisSpacing: 16,
      mainAxisSpacing: 16,
      children: const [
        DashboardCard(title: 'Users'),
        DashboardCard(title: 'Orders'),
        DashboardCard(title: 'Revenue'),
        DashboardCard(title: 'Products'),
      ],
    );
  }
}

class DesktopContent extends StatelessWidget {
  const DesktopContent({super.key});

  @override
  Widget build(BuildContext context) {
    return Center(
      child: ConstrainedBox(
        constraints: const BoxConstraints(
          maxWidth: 1200,
        ),
        child: Row(
          children: const [
            SizedBox(
              width: 240,
              child: DashboardSidebar(),
            ),
            VerticalDivider(width: 1),
            Expanded(
              child: DesktopDashboard(),
            ),
          ],
        ),
      ),
    );
  }
}

class DashboardSidebar extends StatelessWidget {
  const DashboardSidebar({super.key});

  @override
  Widget build(BuildContext context) {
    return ListView(
      children: const [
        ListTile(
          leading: Icon(Icons.dashboard),
          title: Text('Dashboard'),
        ),
        ListTile(
          leading: Icon(Icons.shopping_cart),
          title: Text('Orders'),
        ),
        ListTile(
          leading: Icon(Icons.people),
          title: Text('Users'),
        ),
      ],
    );
  }
}

class DesktopDashboard extends StatelessWidget {
  const DesktopDashboard({super.key});

  @override
  Widget build(BuildContext context) {
    return GridView.count(
      padding: const EdgeInsets.all(24),
      crossAxisCount: 2,
      crossAxisSpacing: 20,
      mainAxisSpacing: 20,
      children: const [
        DashboardCard(title: 'Users'),
        DashboardCard(title: 'Orders'),
        DashboardCard(title: 'Revenue'),
        DashboardCard(title: 'Products'),
      ],
    );
  }
}

class DashboardCard extends StatelessWidget {
  final String title;

  const DashboardCard({
    super.key,
    required this.title,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Center(
        child: Text(
          title,
          style: Theme.of(context).textTheme.titleLarge,
        ),
      ),
    );
  }
}




45. Official Flutter Resources





46. Flutter Course Resources


For structured Flutter learning and training resources, visit:





47. Quick Revision

















ConceptBest Practice
Screen measurementUse MediaQuery.sizeOf or LayoutBuilder.
Local responsive widgetUse LayoutBuilder.
BreakpointsChoose them based on layout requirements.
Device detectionAvoid using phone/tablet checks for layout.
OrientationDo not use orientation alone for major layouts.
Large screensUse max-width constraints and multi-column layouts.
NavigationAdapt NavigationBar, NavigationRail, or sidebar according to available space.
GridUse available width and maximum item dimensions.
SpacingUse a consistent spacing system.
InputSupport touch, mouse, trackpad, and keyboard.
StatePreserve important application and scroll state.
AccessibilityRespect text scaling, focus, keyboard navigation, and usable hit areas.
TestingTest multiple sizes, orientations, and window configurations.



48. Key Takeaways



  • Responsive Flutter design is about adapting UI to the space actually available.

  • Adaptive design goes further by changing UI structures and interactions when necessary.

  • Use MediaQuery.sizeOf for application-window measurements.

  • Use LayoutBuilder when a component should respond to its parent's constraints.

  • Do not assume that phone, tablet, desktop, portrait, or landscape automatically represents a particular amount of space.

  • Use flexible widgets such as Expanded, Flexible, and Wrap.

  • Use responsive grids and maximum content widths.

  • Use SafeArea where content needs protection from system UI and display cutouts.

  • Support touch, mouse, keyboard, and accessibility requirements.

  • Preserve user state during resizing and configuration changes.

  • Break complex interfaces into smaller reusable widgets.

  • Test your application across different window sizes instead of testing only one phone.


Following these practices helps create Flutter applications that remain usable, maintainable, and visually consistent across phones, tablets, foldables, desktops, and web environments.


whatsapp